from peewee import AutoField
from peewee import BigIntegerField
from peewee import BooleanField
from peewee import CharField
from peewee import DateTimeField
from peewee import fn
from peewee import IntegerField
from peewee import Model
from peewee import MySQLDatabase
from peewee import OperationalError
from peewee import SQL
from peewee import TextField

from peewee import __exception_wrapper__

from playhouse.shortcuts import dict_to_model
from playhouse.shortcuts import model_to_dict

from conf.config import DB_CFG


try:
    import MySQLdb
    from _mysql_exceptions import OperationalError as OperationalErrorInMySQLdb
except ImportError:
    class OperationalErrorInMySQLdb(Exception):
        pass


class MyRetryDB(MySQLDatabase):
    # 增加重试的情况，有人遇到如果mysql把当前的连接剔除了，
    # peewee不会自动重连，导致查询失败。
    def execute_sql(self, sql, params=None, commit=True):
        # 解决由于db连接失败，但database连接标示位未置为close状态，导致的出现InterfaceError错误
        try:
            self.connection().ping()
            cursor = super(MyRetryDB, self).execute_sql(sql, params, commit)
        except (OperationalError, OperationalErrorInMySQLdb):
            if not self.is_closed():
                self.close()
            with __exception_wrapper__:
                cursor = self.cursor()
                cursor.execute(sql, params or ())
                if commit and self.in_transaction():
                    self.commit()
        except Exception as e:
            import sys
            exc_type, exc_value, exc_traceback_obj = sys.exc_info()
            LOG.error(exc_type.__name__)
            LOG.error('{0}|{1}|{2}'.format(exc_type, exc_value, exc_traceback_obj))
            LOG.exception(e)
            raise
        return cursor


# http://docs.peewee-orm.com/en/latest/peewee/api.html#database
# Automatically rollback queries that fail when not in an explicit transaction.
database = MyRetryDB(DB_CFG['db'], **{
    'host': DB_CFG['host'],
    'password': DB_CFG['password'],
    'port': DB_CFG['port'],
    'user': DB_CFG['user'],
    'charset': 'utf8',
    'autorollback': True,
})


class BaseModel(Model):
    @classmethod
    def create_from_dict(cls, data):
        return dict_to_model(cls, data, ignore_unknown=True)

    def dict(self):
        return model_to_dict(self)

    @classmethod
    def get_many(cls, order_by_values=None, offset=0, limit=None, **filters):
        expressions = cls._make_where_expressions(**filters)
        query = cls.select()
        if len(expressions) > 0:
            query = query.where(*expressions)

        if order_by_values:
            query = query.order_by(order_by_values)

        if limit:
            query = query.limit(limit).offset(offset)

        return query

    @classmethod
    def get_one_or_none(cls, **filters):
        data = cls.get_many(**filters)
        if len(data) == 0:
            return None

        return data[0]

    @classmethod
    def get_count(cls, **filters):
        expressions = cls._make_where_expressions(**filters)
        query = cls.select(fn.COUNT("*"))
        if len(expressions) > 0:
            query = query.where(*expressions)

        return query.scalar()

    @classmethod
    def insert_one(cls, **kwargs):
        for k in list(kwargs.keys()):
            if kwargs[k] is None:
                kwargs.pop(k)

        with database.atomic():
            item = cls.create(**kwargs)
        return item.id

    @classmethod
    def delete_many(cls, **filters):
        with database.atomic():
            return (cls
                    .delete()
                    .where(*cls._make_where_expressions(**filters))
                    .execute())

    @classmethod
    def update_many(cls, update_map, **filters):
        with database.atomic():
            return (cls
                    .update(**update_map)
                    .where(*cls._make_where_expressions(**filters))
                    .execute())

    @classmethod
    def _make_where_expressions(cls, **filters):
        expressions = []
        for field, value in filters.items():
            if value is None:
                continue
            if isinstance(value, list) or isinstance(value, tuple):
                cnt = len(value)
                if cnt == 0:
                    continue
                elif cnt == 1:
                    expressions.append(getattr(cls, field) == value[0])
                else:
                    expressions.append(getattr(cls, field).in_(value))
            else:
                expressions.append(getattr(cls, field) == value)

        return expressions

    class Meta:
        database = database
        only_save_dirty = True


class BusinessLockModel(BaseModel):
    id = AutoField()
    param = CharField(max_length=128, unique=True)
    description = CharField(max_length=128)
    expired = IntegerField()
    updated_time = DateTimeField(constraints=[SQL('DEFAULT CURRENT_TIMESTAMP')])

    class Meta:
        table_name = 'business_lock'
